﻿namespace B83.ExpressionParserVector3
{
    using System.Linq;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public interface IVectorValue
    {
        Vector3 Value { get; }
    }

    public class Vector3Number : IVectorValue
    {

        private Vector3 m_Value;

        public Vector3 Value
        {
            get { return m_Value; }
            set { m_Value = value; }
        }

        public Vector3Number(Vector3 val)
        {
            m_Value = val;
        }

        public override string ToString()
        {
            return m_Value.ToString();
        }
    }

    public class Vector3OperationSum : IVectorValue
    {

        private readonly IVectorValue[] m_Values;

        public Vector3 Value
        {
            get
            {
                Vector3 sum = Vector3.zero;
                for (int i = 0; i < m_Values.Length; i++)
                {
                    sum += m_Values[i].Value;
                }

                return sum;
            }
        }

        public Vector3OperationSum(params IVectorValue[] aValues)
        {
            var v = new List<IVectorValue>(aValues.Length);
            for (int i = 0; i < aValues.Length; i++)
            {
                var sum = aValues[i] as Vector3OperationSum;
                if (null == sum)
                {
                    v.Add(aValues[i]);
                }
                else
                {
                    v.AddRange(sum.m_Values);
                }
            }

            m_Values = v.ToArray();
        }

        public override string ToString()
        {
            return Value.ToString();
        }
    }

    public class Vector3OperationNegate : IVectorValue
    {
        private IVectorValue m_Value;

        public Vector3 Value
        {
            get { return (-1) * m_Value.Value; }
        }

        public Vector3OperationNegate(IVectorValue aValue)
        {
            m_Value = aValue;
        }

        public override string ToString()
        {
            return Value.ToString();
        }
    }

    public class MultiParameterList : IVectorValue
    {
        private IVectorValue[] m_Values;

        public IVectorValue[] Parameters { get { return m_Values; } }

        public Vector3 Value
        {
            get { return m_Values.Select(v => v.Value).FirstOrDefault(); }
        }

        public MultiParameterList(params IVectorValue[] aValues)
        {
            m_Values = aValues;
        }

        public override string ToString()
        {
            return string.Join(", ", m_Values.Select(v => v.ToString()).ToArray());
        }
    }

    public class CustomFunction : IVectorValue
    {
        private IVectorValue[] m_Params;
        private System.Func<Vector3[], Vector3> m_Delegate;
        private string m_Name;

        public Vector3 Value
        {
            get
            {
                if (m_Params == null)
                    return m_Delegate(null);
                return m_Delegate(m_Params.Select(p => p.Value).ToArray());
            }
        }

        public CustomFunction(string aName, System.Func<Vector3[], Vector3> aDelegate, params IVectorValue[] aValues)
        {
            m_Delegate = aDelegate;
            m_Params = aValues;
            m_Name = aName;
        }

        public override string ToString()
        {
            if (m_Params == null)
                return m_Name;
            return m_Name + "( " + string.Join(", ", m_Params.Select(v => v.ToString()).ToArray()) + " )";
        }
    }

    public class Parameter : Vector3Number
    {
        public string Name { get; private set; }

        public override string ToString()
        {
            return Name + "[" + base.ToString() + "]";
        }

        public Parameter(string aName)
            : base(Vector3.zero)
        {
            Name = aName;
        }
    }

    public class Expression : IVectorValue
    {
        public Dictionary<string, Parameter> Parameters = new Dictionary<string, Parameter>();

        public IVectorValue ExpressionTree { get; set; }

        public Vector3 Value
        {
            get { return ExpressionTree.Value; }
        }

        public Vector3[] MultiValue
        {
            get
            {
                var t = ExpressionTree as MultiParameterList;
                if (t != null)
                {
                    Vector3[] res = new Vector3[t.Parameters.Length];
                    for (int i = 0; i < res.Length; i++)
                        res[i] = t.Parameters[i].Value;
                    return res;
                }
                return null;
            }
        }

        public override string ToString()
        {
            return ExpressionTree.ToString();
        }

        public ExpressionDelegate ToDelegate(params string[] aParamOrder)
        {
            var parameters = new List<Parameter>(aParamOrder.Length);
            for (int i = 0; i < aParamOrder.Length; i++)
            {
                if (Parameters.ContainsKey(aParamOrder[i]))
                    parameters.Add(Parameters[aParamOrder[i]]);
                else
                    parameters.Add(null);
            }
            var parameters2 = parameters.ToArray();

            return (p) => Invoke(p, parameters2);
        }

        public MultiResultDelegate ToMultiResultDelegate(params string[] aParamOrder)
        {
            var parameters = new List<Parameter>(aParamOrder.Length);
            for (int i = 0; i < aParamOrder.Length; i++)
            {
                if (Parameters.ContainsKey(aParamOrder[i]))
                    parameters.Add(Parameters[aParamOrder[i]]);
                else
                    parameters.Add(null);
            }
            var parameters2 = parameters.ToArray();


            return (p) => InvokeMultiResult(p, parameters2);
        }

        Vector3 Invoke(Vector3[] aParams, Parameter[] aParamList)
        {
            int count = System.Math.Min(aParamList.Length, aParams.Length);
            for (int i = 0; i < count; i++)
            {
                if (aParamList[i] != null)
                    aParamList[i].Value = aParams[i];
            }
            return Value;
        }

        Vector3[] InvokeMultiResult(Vector3[] aParams, Parameter[] aParamList)
        {
            int count = System.Math.Min(aParamList.Length, aParams.Length);
            for (int i = 0; i < count; i++)
            {
                if (aParamList[i] != null)
                    aParamList[i].Value = aParams[i];
            }
            return MultiValue;
        }

        public static Expression Parse(string aExpression)
        {
            return new ExpressionParserEx().EvaluateExpression(aExpression);
        }

        public class ParameterException : System.Exception
        {
            public ParameterException(string aMessage)
                : base(aMessage)
            {
            }

        }
    }
    public delegate Vector3 ExpressionDelegate(params Vector3[] aParams);
    public delegate Vector3[] MultiResultDelegate(params Vector3[] aParams);

    public class ExpressionParserEx
    {
        private List<string> m_BracketHeap = new List<string>();
        private static readonly Dictionary<string, System.Func<Vector3[], Vector3>> m_Funcs = new Dictionary<string, System.Func<Vector3[], Vector3>>();
        private Expression m_Context;

        static ExpressionParserEx()
        {

            m_Funcs.Add("rnd", (p) =>
                {
                    if (p.Length == 2)
                    {
                        return new Vector3(
                            Random.Range( System.Math.Min(p[0].x, p[1].x), System.Math.Max(p[0].x, p[1].x)),
                            Random.Range( System.Math.Min(p[0].y, p[1].y), System.Math.Max(p[0].y, p[1].y)),
                            Random.Range( System.Math.Min(p[0].z, p[1].z), System.Math.Max(p[0].z, p[1].z))
                        );
                    }

                    if (p.Length == 1)
                    {
                        return new Vector3(
                            Random.Range( System.Math.Min(p[0].x, 0), System.Math.Max(p[0].x, 0)),
                            Random.Range( System.Math.Min(p[0].y, 0), System.Math.Max(p[0].y, 0)),
                            Random.Range( System.Math.Min(p[0].z, 0), System.Math.Max(p[0].z, 0))
                        );
                    }

                    return Vector3.zero;

                });
        }

        public ExpressionParserEx()
        {
        }

        int FindClosingBracket(ref string aText, int aStart, char aOpen, char aClose)
        {
            int counter = 0;
            for (int i = aStart; i < aText.Length; i++)
            {
                if (aText[i] == aOpen)
                    counter++;
                if (aText[i] == aClose)
                    counter--;
                if (counter == 0)
                    return i;
            }
            return -1;
        }

        void SubstitudeBracket(ref string aExpression, int aIndex)
        {
            int closing = FindClosingBracket(ref aExpression, aIndex, '(', ')');
            if (closing > aIndex + 1)
            {
                string inner = aExpression.Substring(aIndex + 1, closing - aIndex - 1);
                m_BracketHeap.Add(inner);
                string sub = "&" + (m_BracketHeap.Count - 1) + ";";
                aExpression = aExpression.Substring(0, aIndex) + sub + aExpression.Substring(closing + 1);
            }
            else
                throw new ParseException("Bracket not closed!");
        }

        IVectorValue Parse(string aExpression)
        {
            aExpression = aExpression.Trim();
            int index = aExpression.IndexOf('(');
            while (index >= 0)
            {
                SubstitudeBracket(ref aExpression, index);
                index = aExpression.IndexOf('(');
            }
            if (aExpression.Contains(','))
            {
                string[] parts = aExpression.Split(',');
                List<IVectorValue> exp = new List<IVectorValue>(parts.Length);
                for (int i = 0; i < parts.Length; i++)
                {
                    string s = parts[i].Trim();
                    if (!string.IsNullOrEmpty(s))
                        exp.Add(Parse(s));
                }
                return new MultiParameterList(exp.ToArray());
            }
            else if (aExpression.Contains('+'))
            {
                string[] parts = aExpression.Split('+');
                List<IVectorValue> exp = new List<IVectorValue>(parts.Length);
                for (int i = 0; i < parts.Length; i++)
                {
                    string s = parts[i].Trim();
                    if (!string.IsNullOrEmpty(s))
                        exp.Add(Parse(s));
                }
                if (exp.Count == 1)
                    return exp[0];
                return new Vector3OperationSum(exp.ToArray());
            }
            else if (aExpression.Contains('-'))
            {
                string[] parts = aExpression.Split('-');
                List<IVectorValue> exp = new List<IVectorValue>(parts.Length);
                if (!string.IsNullOrEmpty(parts[0].Trim()))
                    exp.Add(Parse(parts[0]));
                for (int i = 1; i < parts.Length; i++)
                {
                    string s = parts[i].Trim();
                    if (!string.IsNullOrEmpty(s))
                        exp.Add(new Vector3OperationNegate(Parse(s)));
                }
                if (exp.Count == 1)
                    return exp[0];
                return new Vector3OperationSum(exp.ToArray());
            }

            foreach (var M in m_Funcs)
            {
                if (aExpression.StartsWith(M.Key))
                {
                    var inner = aExpression.Substring(M.Key.Length);
                    var param = Parse(inner);
                    var multiParams = param as MultiParameterList;
                    IVectorValue[] parameters;
                    if (multiParams != null)
                        parameters = multiParams.Parameters;
                    else
                        parameters = new IVectorValue[] { param };
                    return new CustomFunction(M.Key, M.Value, parameters);
                }
            }

            int index2a = aExpression.IndexOf('&');
            int index2b = aExpression.IndexOf(';');
            if (index2a >= 0 && index2b >= 2)
            {
                var inner = aExpression.Substring(index2a + 1, index2b - index2a - 1);
                int bracketIndex;
                if (int.TryParse(inner, out bracketIndex) && bracketIndex >= 0 && bracketIndex < m_BracketHeap.Count)
                {
                    return Parse(m_BracketHeap[bracketIndex]);
                }
                else
                    throw new ParseException("Can't parse substitude token");
            }
//            Vector3 doubleValue;
//            if (double.TryParse(aExpression, out doubleValue))
//            {
//                return new Vector3Number(doubleValue);
//            }
            if (ValidIdentifier(aExpression))
            {
                if (m_Context.Parameters.ContainsKey(aExpression))
                    return m_Context.Parameters[aExpression];
                var val = new Parameter(aExpression);
                m_Context.Parameters.Add(aExpression, val);
                return val;
            }

            throw new ParseException("Reached unexpected end within the parsing tree");
        }

        private bool ValidIdentifier(string aExpression)
        {
            aExpression = aExpression.Trim();
            if (string.IsNullOrEmpty(aExpression))
                return false;
            if (aExpression.Length < 1)
                return false;
            if (aExpression.Contains(" "))
                return false;
            if (!"abcdefghijklmnopqrstuvwxyz§$".Contains(char.ToLower(aExpression[0])))
                return false;
            if (m_Funcs.ContainsKey(aExpression))
                return false;
            return true;
        }

        public Expression EvaluateExpression(string aExpression)
        {
            var val = new Expression();
            m_Context = val;
            val.ExpressionTree = Parse(aExpression);
            m_Context = null;
            m_BracketHeap.Clear();
            return val;
        }

        public class ParseException : System.Exception
        {
            public ParseException(string aMessage)
                : base(aMessage)
            {
            }

        }
    }
}
